package com.zpp.system.service.exam.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.zpp.common.core.constants.Constants;
import com.zpp.common.core.enums.ResultCode;
import com.zpp.common.security.exception.ServiceException;
import com.zpp.system.domain.exam.Exam;
import com.zpp.system.domain.exam.ExamQuestion;
import com.zpp.system.domain.exam.dto.ExamAddDTO;
import com.zpp.system.domain.exam.dto.ExamEditDTO;
import com.zpp.system.domain.exam.dto.ExamQueryDTO;
import com.zpp.system.domain.exam.dto.ExamQuestionAddDTO;
import com.zpp.system.domain.exam.vo.ExamDetailVO;
import com.zpp.system.domain.exam.vo.ExamQuestionVO;
import com.zpp.system.domain.exam.vo.ExamVO;
import com.zpp.system.domain.question.Question;
import com.zpp.system.manager.ExamCacheManager;
import com.zpp.system.mapper.exam.ExamMapper;
import com.zpp.system.mapper.exam.ExamQuestionMapper;
import com.zpp.system.mapper.question.QuestionMapper;
import com.zpp.system.service.exam.IExamService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;

@Service
public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper, ExamQuestion> implements IExamService {

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private ExamQuestionMapper examQuestionMapper;

    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private ExamCacheManager examCacheManager;

    @Override
    public List<ExamVO> list(ExamQueryDTO examQueryDTO) {
        //设置分页
        PageHelper.startPage(examQueryDTO.getPageNum(), examQueryDTO.getPageSize());

        return examMapper.selectExamList(examQueryDTO);
    }

    @Override
    public String add(ExamAddDTO examAddDTO) {
        //检查参数是否合法
        checkExamSaveParams(examAddDTO, null);

        Exam exam = new Exam();
        BeanUtil.copyProperties(examAddDTO, exam);
        examMapper.insert(exam);

        return exam.getExamId().toString();
    }

    @Override
    public boolean questionAdd(ExamQuestionAddDTO examQuestionAddDTO) {
        //判断竞赛id在数据库中是否存在
        Exam exam = getExam(examQuestionAddDTO.getExamId());
        //竞赛已经发布则不能进行操作
        if(Constants.TRUE.equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }
        //检查是否已经开赛
        checkExam(exam);
        //获取题目id集合
        LinkedHashSet<Long> questionIdSet = examQuestionAddDTO.getQuestionIdSet();
        //判断添加的题目数量为0
        if(CollectionUtil.isEmpty(questionIdSet)) {
            return true;
        }

        //判断题目是否存在
        List<Question> questionList = questionMapper.selectBatchIds(questionIdSet);
        if(CollectionUtil.isEmpty(questionList) || questionList.size() < questionIdSet.size()) {
            throw new ServiceException(ResultCode.EXAM_QUESTION_NOT_EXISTS);
        }

        int order = 1;
        List<ExamQuestion> examQuestionList = new ArrayList<>();
        for(Long questionId : questionIdSet) {
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setExamId(exam.getExamId());
            examQuestion.setQuestionId(questionId);
            examQuestion.setQuestionOrder(order++);
            //将每个竞赛对应的每个题目对象，存入集合中
            examQuestionList.add(examQuestion);
        }

        //一次性向数据库中添加多条数据
        return saveBatch(examQuestionList);
    }

    @Override
    public int questionDelete(Long examId, Long questionId) {
        Exam exam = getExam(examId);
        //检查竞赛是否已经开始
        checkExam(exam);

        return examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId)
                .eq(ExamQuestion::getQuestionId, questionId));
    }

    @Override
    public ExamDetailVO detail(Long examId) {
        //获取竞赛标题和竞赛时间信息
        Exam exam = getExam(examId);

        ExamDetailVO examDetailVO = new ExamDetailVO();
        BeanUtil.copyProperties(exam, examDetailVO);

        //查询当前竞赛对应的所有题目
        List<ExamQuestion> examQuestionList = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId, examId)
                .orderByAsc(ExamQuestion::getQuestionOrder));
        if(CollectionUtil.isEmpty(examQuestionList)) {
            return examDetailVO;
        }
        //提取出每个对象的questionId字段
        List<Long> questionIdList = examQuestionList.stream().map(ExamQuestion::getQuestionId).toList();

        //根据题目id查询对应的题目信息
        //select * from tb_question where question_id in (1,2,3)
        List<Question> questionList = questionMapper.selectList(new LambdaQueryWrapper<Question>()
                .select(Question::getQuestionId, Question::getTitle, Question::getDifficulty)
                .in(Question::getQuestionId, questionIdList));

        List<ExamQuestionVO> examQuestionVOList = BeanUtil.copyToList(questionList, ExamQuestionVO.class);
        examDetailVO.setExamQuestionList(examQuestionVOList);

        return examDetailVO;
    }

    @Override
    public int edit(ExamEditDTO examEditDTO) {
        Exam exam = getExam(examEditDTO.getExamId());
        //竞赛已经发布则不能进行操作
        if(Constants.TRUE.equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }
        //检查是否已经开赛
        checkExam(exam);
        //检查要修改的参数是否合法
        checkExamSaveParams(examEditDTO, examEditDTO.getExamId());

        exam.setTitle(examEditDTO.getTitle());
        exam.setStartTime(examEditDTO.getStartTime());
        exam.setEndTime(examEditDTO.getEndTime());
        //更新数据
        return examMapper.updateById(exam);
    }

    @Override
    public int delete(Long examId) {
        //拿到竞赛对象，检查是否在数据库中存在
        Exam exam = getExam(examId);
        //竞赛已经发布则不能进行操作
        if(Constants.TRUE.equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }
        //判断当前竞赛是否已经开赛
        checkExam(exam);

        //删除竞赛题目表中的信息
        examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));

        //删除竞赛表中的信息
        return examMapper.deleteById(exam);
    }

    @Override
    public int publish(Long examId) {
        //判断竞赛是否存在
        Exam exam = getExam(examId);
        //已经结束的竞赛不能发布
        checkExamPublishStatus(exam);

        //查询当前竞赛下题目数量
        Long count = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>().
                eq(ExamQuestion::getExamId, examId));
        if(count == null || count <= 0) {
            throw  new ServiceException(ResultCode.EXAM_NOT_HAS_QUESTION);
        }
        exam.setStatus(Constants.TRUE);
        examCacheManager.addCache(exam); //在redis缓存中添加数据

        return examMapper.updateById(exam);
    }

    @Override
    public int cancelPublish(Long examId) {
        //判断竞赛是否存在
        Exam exam = getExam(examId);
        //判断是否已经开赛
        checkExam(exam);
        //已经结束的竞赛不能撤销发布
        checkExamPublishStatus(exam);

        exam.setStatus(Constants.FALSE);
        examCacheManager.deleteCache(examId); //清除redis缓存中的数据

        return examMapper.updateById(exam);
    }

    public Exam getExam(Long examId) {
        Exam exam = examMapper.selectById(examId);
        if(exam == null) {
            //不存在则抛出异常
            throw new ServiceException(ResultCode.EXAM_NOT_EXISTS);
        }
        return exam;
    }

    private void checkExamSaveParams(ExamAddDTO examSaveDTO, Long examId) {
        //检验竞赛标题是否存在
        List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .eq(Exam::getTitle, examSaveDTO.getTitle())
                .ne(examId != null, Exam::getExamId, examId));  //不查询
        if(CollectionUtil.isNotEmpty(examList)) {
            throw new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);
        }
        //不允许设置的开始时间比当前时间早
        if(examSaveDTO.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_BEFORE_CURRENT_TIME);
        }
        //不允许设置的开始时间比结束时间晚
        if(examSaveDTO.getStartTime().isAfter(examSaveDTO.getEndTime())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_AFTER_END_TIME);
        }
    }

    public void checkExam(Exam exam) {
        //判断当前竞赛是否已经开赛
        if(exam.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_STARTED);
        }
    }

    public void checkExamPublishStatus(Exam exam) {
        //不能操作已经结束竞赛的发布状态
        if(exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_IS_FINISH);
        }
    }

}
